package com.sh.data.engine.domain.api.service.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.extra.servlet.JakartaServletUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.google.common.collect.Lists;
import com.sh.data.engine.common.enumDefinition.DataApiCallStatusEnum;
import com.sh.data.engine.common.enumDefinition.DataApiStatusEnum;
import com.sh.data.engine.common.enumDefinition.DataApiTaskTypeEnum;
import com.sh.data.engine.common.exception.BusinessException;
import com.sh.data.engine.common.util.AESUtil;
import com.sh.data.engine.common.util.ServletUtils;
import com.sh.data.engine.domain.api.model.domain.DataApiCallRecordDomain;
import com.sh.data.engine.domain.api.model.domain.DataApiDomain;
import com.sh.data.engine.domain.api.model.domain.DataApiTaskResultDomain;
import com.sh.data.engine.domain.api.model.domain.DataApiTokenDomain;
import com.sh.data.engine.domain.api.model.domain.rest.*;
import com.sh.data.engine.domain.api.model.param.DataApiAddRecordParam;
import com.sh.data.engine.domain.api.model.param.DataApiCommomParam;
import com.sh.data.engine.domain.api.run.DataApiConfig;
import com.sh.data.engine.domain.api.run.DataApiTaskExecutor;
import com.sh.data.engine.domain.api.run.DataApiTaskExecutorFactory;
import com.sh.data.engine.domain.api.service.DataApiAuthService;
import com.sh.data.engine.domain.api.service.DataApiCallRecordService;
import com.sh.data.engine.domain.api.service.DataApiRestService;
import com.sh.data.engine.domain.api.service.DataApiService;
import com.sh.data.engine.domain.api.util.DataApiUtil;
import com.sh.data.engine.domain.base.model.UserContext;
import com.sh.data.engine.domain.common.model.domain.DataApiKey;
import com.sh.data.engine.repository.dao.api.entity.DataApiEntity;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingServletRequestParameterException;

import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.sh.data.engine.domain.api.model.param.DataApiCommomParam.APITOKEN;
import static com.sh.data.engine.domain.api.model.param.DataApiCommomParam.USERTOKEN;

@Service
@Slf4j
public class DataApiRestServiceImpl implements DataApiRestService, ApplicationRunner {

    @Lazy
    @Autowired
    private DataApiService dataApiService;

    @Lazy
    @Autowired
    private DataApiAuthService dataApiAuthService;

    @Lazy
    @Autowired
    private DataApiCallRecordService dataApiCallRecordService;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private DataApiConfig dataApiConfig;

    public static final int MAX_CACHE_NUMBER = 1000;

    private static final String DATA_API_CALL_KEY = "data_api_call_key_";

    /***
     * @param apiPath
     * @param request
     * @return
     *
     * 1.校验请求信息
     *  a.token信息
     *  b.param必填参数校验
     *
     * 2.无参数给予默认值
     *
     * 3.生层访问记录（手动调动、生产调用）
     *
     * 4.返回结果，封装结果
     */

    @Override
    public Object getResult(
        String apiPath, DataApiHttpServletRequestPayload requestPayload, HttpServletRequest request)
        throws Exception {

        DataApiCallRecordDomain apiRecord = null;
        try {
            Map<String, String> params = requestPayload.getParams();

            String apiToken = params.get(APITOKEN);
            String userToken = params.get(USERTOKEN);
            if (StringUtils.isBlank(apiToken) && StringUtils.isBlank(userToken)) {
                throw new BusinessException(String.format("请求中未携带[%s]", APITOKEN));
            }
            String token = null;
            if (StringUtils.isNotBlank(apiToken)) {
                token = apiToken;
            } else {
                token = userToken;
            }
//
            if (token.length() > 40) {
                String dataApiSecretType = dataApiConfig.getDataApiSecretType();
                String dataApiKey = dataApiConfig.getDataApiKey();
                token = AESUtil.decrypt(token, dataApiKey, dataApiSecretType);
            }

            DataApiTokenDomain apiTokenDomain = dataApiAuthService.getApiIdByToken(token, apiPath);

            if (null == apiTokenDomain) {
                throw new BusinessException("token无效");
            }

            Integer isSecurity = apiTokenDomain.getIsSecurity();

            DataApiKey dataApiKey = DataApiKey.of(apiTokenDomain.getApiId(), apiPath);
            DataApiConfigInfoDomain apiConfig;
            if (!apiTokenDomain.getIsProd()) {
                apiConfig = dataApiService.getDataApiConfig(apiTokenDomain.getApiId());
                isSecurity = apiConfig.getIsSecurity();
            } else {
                apiConfig = loadDataApiConfig(dataApiKey);
            }

            // api数据入参解密
            if (Objects.nonNull(isSecurity) && isSecurity.equals(1)) {
                String dataApiSecret = dataApiConfig.getDataApiKey();
                String dataApiSecretType = dataApiConfig.getDataApiSecretType();
                Iterator<Entry<String, String>> iterator = params.entrySet().iterator();
                while (iterator.hasNext()) {
                    Entry<String, String> next = iterator.next();
                    String value = next.getValue();
                    String key = next.getKey();

                    String decrypt = AESUtil.decrypt(value, dataApiSecret, dataApiSecretType);
                    params.put(key, decrypt);
                    if (StringUtils.isBlank(decrypt)) {
                        throw new BusinessException("参数:" + key + "的参数值需要加密");
                    }
                }
            }
            //


            Long projectId = apiConfig.getProjectId();
            String clientIP = JakartaServletUtil.getClientIP(request, null);
            // 生成访问记录
            DataApiAddRecordParam param = new DataApiAddRecordParam();
            param.setProjectId(projectId);
            param.setUserId(apiTokenDomain.getUserId());
            param.setDataApiId(apiConfig.getId());
            param.setCallType(apiTokenDomain.getIsProd() ? 1 : 0);
            param.setRequestAddress(clientIP);
            apiRecord = dataApiCallRecordService.addRecord(param);

            // 校验请求的细节：apiProtocal/apiMethod等，测试请求不用校验
            checkRequest(
                apiConfig,
                apiRecord,
                requestPayload.getRequestMethod(),
                requestPayload.getRequestProtocol());

            // 校验请求的状态apiStatus：除非是测试请求，否则必须发布后的接口才允许正式返回数据
            checkApiStatus(apiConfig, apiRecord);

            // 校验共工请求参数
            checkCommonParams(apiConfig, apiRecord, params);

            // 给空字段设值默认值
            setDefaultValue(apiConfig, params);

            // 校验必填参数
            checkRequestParams(apiConfig, apiRecord, params);

            // 根据DataApiConfigInfo的配置信息到指定数据源获取指定表的指定数据列集合
            Object result = doService(apiConfig, params, apiRecord);

            return result;
        } finally {
            if (null != apiRecord && null != apiRecord.getCallStatus()) {
                // 更新访问记录状态
                apiRecord.setCallEndTime(new Date());
                apiRecord.setIsProcessed(0);
                String jsonString = JSONObject.toJSONString(apiRecord);
                String key = DATA_API_CALL_KEY + apiRecord.getId();
                redisTemplate.opsForValue().set(key, jsonString, 10, TimeUnit.MINUTES);
            }
        }
    }

    @Override
    public Long testDataApi(DataApiDomain dataApiDomain, Map<String, String> params) {
        Assert.notNull(dataApiDomain, "参数不能为空");

        String userId = UserContext.getUserId();
        Long projectId = ServletUtils.getProjectId();

        DataApiCallRecordDomain dataApiCallRecordDomain = null;
        if (Objects.nonNull(dataApiDomain.getId())) {
            DataApiAddRecordParam recordParam =
                DataApiAddRecordParam.builder()
                    .projectId(projectId)
                    .userId(userId)
                    .dataApiId(dataApiDomain.getId())
                    .callType(0)
                    .build();
            dataApiCallRecordDomain = dataApiCallRecordService.addRecord(recordParam);
        }

        DataApiConfigInfoDomain dataApiConfigInfoDomain =
            Convert.convert(DataApiConfigInfoDomain.class, dataApiDomain);
        DataApiConfigInfoDomain dataApiConfigInfoByType =
            dataApiService.getDataApiConfigInfoByType(dataApiConfigInfoDomain, dataApiDomain);
        long start = System.currentTimeMillis();
        long l = System.currentTimeMillis();
        DataApiTaskExecutor executor =
            DataApiTaskExecutorFactory.getExecutor(null, dataApiConfigInfoByType, params, l);
        Integer status = DataApiCallStatusEnum.SUCCESS.getStatus();
        try {
            DataApiTaskResultDomain resultDomain = executor.run();
            String parseResult = this.parseResult(resultDomain.getOutput(), null, l, null);

            DataApiUtil.writeResponse(l, parseResult);

        } catch (Exception e) {
            log.error("请求数据失败", e);
            String line =
                String.format(
                    "[%s] api request failed", DateFormatUtils.format(new Date(), "HH:mm:ss.SSS"));
            /** 调用状态 0-成功 1-超时 2-未认证 3-参数错误 4-系统错误 5-其他 */
            DataApiUtil.writeLog(l, Lists.newArrayList(line, ExceptionUtils.getFullStackTrace(e)));
            status = DataApiCallStatusEnum.SYSTEM_ERR.getStatus();
        } finally {
            if (Objects.nonNull(dataApiDomain.getId())) {
                dataApiCallRecordDomain.setCallEndTime(new Date());
                dataApiCallRecordDomain.setIsProcessed(0);
                dataApiCallRecordDomain.setCallStatus(status);
                String jsonString = JSONObject.toJSONString(dataApiCallRecordDomain);
                String key = DATA_API_CALL_KEY + dataApiCallRecordDomain.getId();
                redisTemplate.opsForValue().set(key, jsonString);
            }
        }
        long endTime = System.currentTimeMillis();
        long time = endTime - start;
        String line =
            String.format(
                "[%s] api request finished,take time %s ms",
                DateFormatUtils.format(new Date(), "HH:mm:ss.SSS"), time);
        DataApiUtil.writeLog(l, Lists.newArrayList(line));
        return l;
    }

    @Override
    public boolean removeConfigCache(Long apiId, String apiPath) {
        DataApiKey dataApiKey = DataApiKey.of(apiId, apiPath);
        String s = JSON.toJSONString(dataApiKey);
        redisTemplate.delete(s);
        return true;
    }

    private String parseResult(Object result, DataApiCallRecordDomain apiRecord, Long id, Object o) {
        if (null != result) {
            String jsonString;
            if (result instanceof String) {
                jsonString = (String) result;
            } else {
                jsonString =
                    JSON.toJSONString(
                        result,
                        SerializerFeature.QuoteFieldNames,
                        SerializerFeature.WriteMapNullValue,
                        SerializerFeature.WriteDateUseDateFormat);
            }

            // 截取15000个字符
            if (jsonString.length() > 15000) {
                jsonString = jsonString.substring(0, 15000);

                String log =
                    String.format(
                        "[%s] api result more than 15000 characters, will format to STRING type [not JSON]. if you want see all ,please use api url to test",
                        DateFormatUtils.format(new Date(), "HH:mm:ss.SSS"));

                DataApiUtil.writeLog(id, Lists.newArrayList(log));
            }
            return jsonString;
        }
        return "";
    }

    private Object doService(
        DataApiConfigInfoDomain apiConfig,
        Map<String, String> params,
        DataApiCallRecordDomain apiRecord) {

        try {
            DataApiTaskExecutor executor =
                DataApiTaskExecutorFactory.getExecutor(apiRecord, apiConfig, params, null);
            DataApiTaskResultDomain resultDomain = executor.run();
            apiRecord.setCallStatus(resultDomain.getStatus());
            return resultDomain.getOutput();
        } catch (Exception e) {
            log.error("获取数据失败", e);
            throw new BusinessException(String.format("获取数据异常:[%s]", e.getMessage()));
        }
    }

    private DataApiConfigInfoDomain loadDataApiConfig(DataApiKey apiKey) {
        String toJSONString = JSON.toJSONString(apiKey);
        String s = redisTemplate.opsForValue().get(toJSONString);
        DataApiEntity byId = dataApiService.getById(apiKey.getApiId());
        Integer apiType = byId.getApiType();
        DataApiConfigInfoDomain apiConfig = null;
        if (apiType.equals(DataApiTaskTypeEnum.REGIEST.getTypeCode())) {
            apiConfig = JSON.parseObject(s, DataApiRegisterTaskConfigInfoDomain.class);
        } else if (apiType.equals(DataApiTaskTypeEnum.GUIDE.getTypeCode())) {
            apiConfig = JSON.parseObject(s, DataApiGuideTaskConfigInfoDomain.class);
        } else if (apiType.equals(DataApiTaskTypeEnum.SCRIPT.getTypeCode())) {
            apiConfig = JSON.parseObject(s, DataApiScriptTaskConfigInfoDomain.class);
        } else if (apiType.equals(DataApiTaskTypeEnum.INDEXLIB.getTypeCode())) {
            apiConfig = JSON.parseObject(s, DataApiIndexTaskConfigInfoDomain.class);
        }
        String apiKeyString = JSON.toJSONString(apiKey);
        if (apiConfig == null) {
            apiConfig = dataApiService.getDataApiConfig(apiKey.getApiId());
            if (apiConfig == null) {
                log.error("请求路径或apiToken非法");
                throw new BusinessException("请求路径或apiToken非法");
            }
            String updateApiConfigString = JSON.toJSONString(apiConfig);
            if (apiType.equals(DataApiStatusEnum.PUBLISHED.getStatus())) {
                redisTemplate.opsForValue().set(apiKeyString, updateApiConfigString);
            }
        }
        return apiConfig;
    }

    @Override
    public Object getResult(String apiPath, HttpServletRequest request) throws Exception {
        DataApiHttpServletRequestPayload requestPayload = DataApiUtil.convertRequestToPayload(request);
        return getResult(apiPath, requestPayload, request);
    }

    private boolean checkRequest(
        DataApiConfigInfoDomain apiConfig,
        DataApiCallRecordDomain record,
        String method,
        String protocol)
        throws Exception {

        String apiMethod = apiConfig.getApiMethod();
        String apiProtocal = apiConfig.getApiProtocol();

        List<String> apiProtocols = Lists.newArrayList(StringUtils.split(apiProtocal, ","));
        List<String> t1 =
            apiProtocols.stream()
                .map(
                    e -> {
                        if (e.equals("0")) {
                            return "HTTP";
                        } else {
                            return "HTTPS";
                        }
                    })
                .collect(Collectors.toList());
        Optional<String> optional =
            t1.stream().filter(p -> StringUtils.equalsIgnoreCase(p, protocol)).findFirst();

        if (!StringUtils.equalsIgnoreCase(method, apiMethod)) {
            record.setCallStatus(DataApiCallStatusEnum.PARAM_ERR.getStatus());
            log.error("不支持的请求方法{}", method);
            throw new HttpRequestMethodNotSupportedException(method);
        }
        if (!optional.isPresent()) {
            record.setCallStatus(DataApiCallStatusEnum.PARAM_ERR.getStatus());
            log.error("不支持的请求方法{}", protocol);
            String protocolMsg = String.format("Request protocol '%s' not supported", protocol);
            throw new BusinessException(protocolMsg);
        }
        return true;
    }

    private boolean checkApiStatus(
        DataApiConfigInfoDomain apiConfig, DataApiCallRecordDomain record) {
        Integer apiStatus = apiConfig.getApiStatus();
        if (!apiStatus.equals(DataApiStatusEnum.PUBLISHED.getStatus())) {
            if (record.getCallType() == 1) {
                log.error("非法调用未发布数据接口");
                throw new BusinessException("非法调用未发布的数据接口");
            }
        }
        return true;
    }

    private boolean checkCommonParams(
        DataApiConfigInfoDomain apiConfig,
        DataApiCallRecordDomain apiRecord,
        Map<String, String> params) {
        // 如果不开启分页，则无需校验pageNum和pageSize
        if (null == apiConfig.getPageable() || apiConfig.getPageable() == 0) {
            return true;
        }

        if (params.containsKey(DataApiCommomParam.PAGENUM)) {
            boolean digits = NumberUtils.isDigits(params.get(DataApiCommomParam.PAGENUM));
            if (!digits) {
                apiRecord.setCallStatus(DataApiCallStatusEnum.PARAM_ERR.getStatus());

                throw new BusinessException(
                    String.format("参数值[%s]非法", DataApiCommomParam.PAGENUM));
            }
        }

        if (params.containsKey(DataApiCommomParam.PAGESIZE)) {
            boolean digits = NumberUtils.isDigits(params.get(DataApiCommomParam.PAGESIZE));
            if (!digits) {
                apiRecord.setCallStatus(DataApiCallStatusEnum.PARAM_ERR.getStatus());

                throw new BusinessException(
                    String.format("参数值[%s]非法", DataApiCommomParam.PAGESIZE));
            }
        }
        return true;
    }

    private void setDefaultValue(
        DataApiConfigInfoDomain apiConfig, Map<String, String> actualParams) {
        // 若参数未赋值，但是存在默认值，则使用默认值
        List<DataApiTaskParamDomain> requestParams = apiConfig.getApiParamEntityList();
        if (CollectionUtils.isEmpty(requestParams)) {
            return;
        }

        requestParams.forEach(
            param -> {
                String paramName = param.getParamName();
                String defaultValue = param.getDefaultValue();

                if ((!actualParams.containsKey(paramName)
                    || StringUtils.isBlank(actualParams.get(paramName)))
                    && StringUtils.isNotBlank(defaultValue)) {
                    actualParams.put(paramName, defaultValue);
                }
            });
    }

    private boolean checkRequestParams(
        DataApiConfigInfoDomain apiConfig,
        DataApiCallRecordDomain apiRecord,
        Map<String, String> actualParams)
        throws Exception {
        List<DataApiTaskParamDomain> requestParams = apiConfig.getApiParamEntityList();
        if (CollectionUtils.isEmpty(requestParams)) {
            return true;
        }

        List<DataApiTaskParamDomain> requestMustParams =
            requestParams.stream()
                .filter(e -> (e.getParamRequire() != null && e.getParamRequire() == 1))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(requestMustParams)) {
            return true;
        }

        for (DataApiTaskParamDomain param : requestMustParams) {
            String paramName = param.getParamName();
            if (!actualParams.containsKey(paramName)) {
                log.error("请求参数中没有找到必须参数{}", paramName);
                apiRecord.setCallStatus(DataApiCallStatusEnum.PARAM_ERR.getStatus());
                throw new MissingServletRequestParameterException(paramName, param.getParamDataType());
            }
            if (StringUtils.isBlank(actualParams.get(paramName))) {
                log.error("{}请求参数中必须参数{}值为空", apiConfig.getApiName(), paramName);
                apiRecord.setCallStatus(DataApiCallStatusEnum.PARAM_ERR.getStatus());
                throw new MissingServletRequestParameterException(paramName, param.getParamDataType());
            }
        }

        return true;
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        new Thread(
            () -> {
                try {
                    List<DataApiConfigInfoDomain> dataApiConfigs =
                        dataApiService.getDataApiConfigs(MAX_CACHE_NUMBER);

                    dataApiConfigs.stream()
                        .collect(
                            Collectors.toMap(e -> DataApiKey.of(e.getId(), e.getApiPath()), e -> e));

                    dataApiConfigs.forEach(
                        e -> {
                            String value = null;
                            if (e instanceof DataApiGuideTaskConfigInfoDomain) {
                                DataApiGuideTaskConfigInfoDomain eValue =
                                    (DataApiGuideTaskConfigInfoDomain) e;
                                value = JSON.toJSONString(eValue);
                            } else if (e instanceof DataApiRegisterTaskConfigInfoDomain) {
                                DataApiRegisterTaskConfigInfoDomain eValue =
                                    (DataApiRegisterTaskConfigInfoDomain) e;
                                value = JSON.toJSONString(eValue);
                            } else if (e instanceof DataApiIndexTaskConfigInfoDomain) {
                                DataApiIndexTaskConfigInfoDomain eValue =
                                    (DataApiIndexTaskConfigInfoDomain) e;
                                value = JSON.toJSONString(eValue);
                            } else if (e instanceof DataApiScriptTaskConfigInfoDomain) {
                                DataApiScriptTaskConfigInfoDomain eValue =
                                    (DataApiScriptTaskConfigInfoDomain) e;
                                value = JSON.toJSONString(eValue);
                            }
                            DataApiKey of = DataApiKey.of(e.getId(), e.getApiPath());
                            String key = JSON.toJSONString(of);
                            redisTemplate.opsForValue().set(key, value);
                        });
                    dataApiAuthService.initProdApiToRedis();
                } catch (Exception e) {
                    log.error("加载数据服务数据异常", e);
                }
            })
            .start();
    }

    public static void main(String[] args) {
        String API = "a1b5aa5a7c7344119e24b33ef677cd72";
        String api2 = "d739b5398b005d5d573841a8c88a8fa8eed9776505e3ba9e4f6f81f58e4e765639c4e2738e2ec632d";
        System.out.println(API.length());
        System.out.println(api2.length());
    }
}
